/*
 * @(#)ResourceSupport.java	1.0 2000/12/14
 *
 * Copyright 2000 Thomas Barnekow. All Rights Reserved.
 *
 * This software is the proprietary information of Thomas Barnekow.
 * Use is subject to license terms.
 */

package tb.jots;

import java.util.*;

/**
 * This is a delegate supporting the implementation of {@link Resource}s.
 *
 * @author Thomas Barnekow
 * @version 1.0
 */
public class ResourceSupport implements Resource {

	protected static final Integer VOTE_COMMIT = new Integer(OTS.VOTE_COMMIT);
	protected static final Integer VOTE_ROLLBACK = new Integer(OTS.VOTE_ROLLBACK);
	protected static final Integer VOTE_READ_ONLY = new Integer(OTS.VOTE_READ_ONLY);

  protected static final Object KEY_PREPARED = new Object();
  protected static final Object KEY_VOTE = new Object();
  protected static final Object KEY_HEURISTIC_EXCEPTION = new Object();
  protected static final Object KEY_COORDINATOR = new Object();

	/**
	 * The client <i>Resource</i>.
	 */
	private Resource m_resource;

	/**
	 * The current transaction <i>Coordinator</i>.
	 */
	// private Coordinator m_coordinator;

	/*
	 * Members holding state information
	 */

	// private ThreadLocal m_prepared = new ThreadLocal();
	// private ThreadLocal m_vote = new ThreadLocal();
	// private ThreadLocal m_lastHeuristicException = new ThreadLocal();

	/**
	 * Constructor.
	 *
	 * @param resource	The client <i>Resource</i>
	 */
	public ResourceSupport(Resource resource) {
		m_resource = resource;
	}

  public String toString() {
    return "[ResourceSupport: " + m_resource.toString() + "]";
  }

  /*
   * Transaction data
   */

  private final Map m_contextMaps = new WeakHashMap();

  protected final Map getContextMap(Object context) {
    Map map = (Map) m_contextMaps.get(context);
    if (map == null) {
      map = new HashMap();
      m_contextMaps.put(context, map);
    }
    return map;
  }

  protected final Map removeContextMap(Object context) {
    return (Map) m_contextMaps.remove(context);
  }

  public final Object put(Object context, Object key, Object value) {
    return getContextMap(context).put(key, value);
  }

  public final Object get(Object context, Object key) {
    return getContextMap(context).get(key);
  }

  public final Object remove(Object context, Object key) {
    return getContextMap(context).remove(key);
  }

  /*
   * Transaction support
   */

  /**
   * Require a transaction. Implicitly create a new transaction context if
   * necessary.
   *
   * @see #requireTransaction(boolean)
   */
  public void requireTransaction() throws JotsException {
    requireTransaction(true);
  }

	/**
	 * Require a transaction. The vote is initially set to <i>OTS.VOTE_READ_ONLY</i>.
	 * The transaction is marked as "not prepared". The last <i>HeuristicException</i>
	 * is reset to <i>null</i>.
   *
   * @param createContext create a new transaction context if necessary?
   * @exception NoTransactionException
   * @exception WrongTransactionException
   *
   * @see Current#begin
	 *
	public void requireTransaction(boolean createContext) throws JotsException {
		// Check current transaction.
		Control control = OTS.getCurrent().getControl();
		if (control == null) {
      if (createContext) {
        OTS.getCurrent().begin();
        control = OTS.getCurrent().getControl();
      } else {
  			throw new NoTransactionException();
      }
		}

		// Are we already associated with a transaction?
		if (m_coordinator == null) {
			// No => Join that transaction
			m_coordinator = control.getCoordinator();
			m_coordinator.registerResource(this);
		} else if (!m_coordinator.isSameTransaction(control.getCoordinator())) {
			// Oops, wrong transaction
			throw new WrongTransactionException();
		}

		// Check transaction status
		if (m_coordinator.getStatus() == OTS.STATUS_MARKED_ROLLBACK) {
			throw new TransactionRolledBackException();
		}

		m_prepared.set(Boolean.FALSE);
		m_vote.set(VOTE_READ_ONLY);
		m_lastHeuristicException.set(null);
	}
  */

 	public void requireTransaction(boolean createContext) throws JotsException {
		// Check current transaction.
		Control control = OTS.getCurrent().getControl();
		if (control == null) {
      if (createContext) {
        OTS.getCurrent().begin();
        control = OTS.getCurrent().getControl();
      } else {
  			throw new NoTransactionException();
      }
		}

    Coordinator coordinator = (Coordinator) get(control, KEY_COORDINATOR);
    if (coordinator == null) {
      // Join that transaction
      coordinator = control.getCoordinator();
      coordinator.registerResource(this);
      put(control, KEY_COORDINATOR, coordinator);
    }

    put(control, KEY_PREPARED, Boolean.FALSE);
    put(control, KEY_VOTE, VOTE_READ_ONLY);
    put(control, KEY_HEURISTIC_EXCEPTION, null);
  }

	/**
	 * Set vote to <i>OTS.VOTE_COMMIT</i>, iff the vote has not been set to
	 * <i>OTS.VOTE_ROLLBACK</i> before.
	 *
	public void voteCommit() {
		Integer vote = (Integer) m_vote.get();
		if (vote == null || vote.equals(VOTE_READ_ONLY)) {
			m_vote.set(VOTE_COMMIT);
		}
	}
  */

	public void voteCommit() {
    Control control = OTS.getCurrent().getControl();
		Integer vote = (Integer) get(control, KEY_VOTE);
		if (vote == null || vote.equals(VOTE_READ_ONLY)) {
      put(control, KEY_VOTE, VOTE_COMMIT);
		}
	}

	/**
	 * Set vote to <i>OTS.VOTE_ROLLBACK</i>.
	 * It cannot be set to any other value, afterwards.
	 */
	public void voteRollback() {
		// m_vote.set(VOTE_ROLLBACK);
    put(OTS.getCurrent().getControl(), KEY_VOTE, VOTE_ROLLBACK);
	}

	/**
	 * Get vote.
	 */
	public int getVote() {
		// Integer vote = (Integer) m_vote.get();
    Integer vote = (Integer) get(OTS.getCurrent().getControl(), KEY_VOTE);
		return vote.intValue();
	}

	/**
	 * This method will call <i>Current.rollbackOnly()</i> in order to set
	 * the current transaction's status to <i>OTS.STATUS_MARKED_ROLLBACK</i>.
	 * For the unexpected case that anybody calls <i>Resource.prepare()</i>
	 * the vote is set to <i>OTS.VOTE_ROLLBACK</i>.
	 */
	public void rollbackOnly() throws JotsException {
		OTS.getCurrent().rollbackOnly();
		voteRollback();
	}

	/**
	 * Remember the given <i>HeuristicException</i> as the
	 * last <i>HeuristicException</i>.
	 */
	public HeuristicException remember(HeuristicException he) {
		// m_lastHeuristicException.set(he);
    put(OTS.getCurrent().getControl(), KEY_HEURISTIC_EXCEPTION, he);
		return he;
	}

	/**
	 * Rethrow the last <i>HeuristicException</i>.
	 */
	public void throwLastHeuristicException()
		throws HeuristicCommitException,
					 HeuristicRollbackException,
					 HeuristicMixedException,
					 HeuristicHazardException
	{
		// Exception he = (Exception) m_lastHeuristicException.get();
    Exception he = (Exception) get(OTS.getCurrent().getControl(), KEY_HEURISTIC_EXCEPTION);
		if (he == null) return;

		if (he instanceof HeuristicCommitException) {
			throw (HeuristicCommitException) he;
		} else if (he instanceof HeuristicRollbackException) {
			throw (HeuristicRollbackException) he;
		} else if (he instanceof HeuristicMixedException) {
			throw (HeuristicMixedException) he;
		} else if (he instanceof HeuristicHazardException) {
			throw (HeuristicHazardException) he;
		}
	}

	/*
   * Resource operations
	 */

	/**
	 * Phase 1: Prepare
	 */
	public int prepare() {
		int vote = m_resource.prepare();

    // m_prepared.set(Boolean.TRUE);
    put(OTS.getCurrent().getControl(), KEY_PREPARED, Boolean.TRUE);

		return vote;
	}

	/**
	 * Phase 2: Rollback changes
	 */
	public void rollback()
		throws HeuristicCommitException,
					 HeuristicMixedException,
					 HeuristicHazardException
	{
		// Do nothing if there is no current transaction
		// if (m_coordinator == null) return;
    if (OTS.getCurrent().getControl() == null)
      return;

		try {
			throwLastHeuristicException();
		} catch (HeuristicRollbackException ex) {}

		try {
			m_resource.rollback();
		} catch (HeuristicCommitException ex) {
			throw (HeuristicCommitException) remember(ex);
		} catch (HeuristicMixedException ex) {
			throw (HeuristicMixedException) remember(ex);
		} catch (HeuristicHazardException ex) {
			throw (HeuristicHazardException) remember(ex);
		}

		forget();
	}

	/**
	 * Phase 2: Commit changes.
	 */
	public void commit()
		throws NotPreparedException,
					 HeuristicRollbackException,
					 HeuristicMixedException,
					 HeuristicHazardException
	{
		// Do nothing if there is no current transaction
		// if (m_coordinator == null) return;
    Control control = OTS.getCurrent().getControl();
    if (control == null) return;

		try {
			throwLastHeuristicException();
		} catch (HeuristicCommitException ex) {}

		// Boolean prepared = (Boolean) m_prepared.get();
    Boolean prepared = (Boolean) get(control, KEY_PREPARED);
		if (prepared == null || prepared != Boolean.TRUE) {
			throw new NotPreparedException();
		}

		try {
			m_resource.commit();
		} catch (HeuristicRollbackException ex) {
			throw (HeuristicRollbackException) remember(ex);
		} catch (HeuristicMixedException ex) {
			throw (HeuristicMixedException) remember(ex);
		} catch (HeuristicHazardException ex) {
			throw (HeuristicHazardException) remember(ex);
		}

		forget();
	}

	/**
	 * Commit one phase
	 */
	public void commitOnePhase()
		throws TransactionRolledBackException,
					 HeuristicHazardException
	{
		// Do nothing if there is no current transaction
		// if (m_coordinator == null) return;
    if (OTS.getCurrent().getControl() == null)
      return;

		// Throw only HeuristicHazard exceptions.
		// Mask any other heuristic exception.
		try {
			throwLastHeuristicException();
		} catch (HeuristicHazardException ex) {
			throw ex;
		} catch (HeuristicException ex) {}

		try {
			m_resource.commitOnePhase();
		} catch (HeuristicHazardException ex) {
			throw (HeuristicHazardException) remember(ex);
		}

		forget();
	}

	/**
	 * Forget all about the current transaction.
	 */
	public void forget() {
		// Do nothing if there is no current transaction
    Control control = OTS.getCurrent().getControl();
		if (control == null) return;

		// Reset state
		// m_coordinator = null;

		// m_prepared.set(Boolean.FALSE);
		// m_vote.set(VOTE_READ_ONLY);
		// m_lastHeuristicException.set(null);

    removeContextMap(control);

		// Make resource forget the transaction
		m_resource.forget();
	}
}
